/**
 * mLibPlane.js
 * Created: May 2013
 * Source link: http://code.google.com/p/code-depository/source/browse/
 *
 * By Suresh Nalluri,
 * License: The MIT License (MIT)
 *          See LICENSE details
 *
 *	Todo:
 *			transform TBD
 * Requires:
 *	mLibFactory.js
 *	mLibIncludes.js
 *	mLibVec3.js
 *	mLibLin3.js
 *	mLIB.Quaternion,
 *  mLIB.Mat44;
 *
 * Plane Class
 * A Plane object is composed of a vector, normal to the plane, and an offset value d.
 * We are using the generalised plane equation to represet a plane: ax + bx + cy + d = 0
 * where (a, b, c) is the unit normal to the plane and d = -(aPx + bPy + cPz)
 * (Px, Py, Pz) being a point on the plane
 *
 *
 * note this constructor pattern allows us to create objects in 2 ways:
 * 1. var o = mLib.Plane( 0 or more params );      //OR
 * 2. var o = new mLib.lin3( 0 or more params );
 *
 * NOTE: var o = [new] mLIB.Lin3(<no params>); // will return default line composed of the
 * unit vector in direction i (1,0,0), and the Origin (0,0,0) as the starting point.
 *
 *
 * added benefit of this pattern is that we get a copy constructor by default!!
 * eg. var newPlane = mLIB.Plane(existingPlane);
 * so no need to have an explicit copy method.
 *
 * Usage:
 *		var l = [new] mLIB.Plane([ plane | a,b,c,d | P,Q,R | Normal, Offset | <noargs> ]);
 *
 *-------------------------------------------------------
 **/


if (typeof mLIB == 'undefined') function mLIB() {};

( function(module) {
    module.Plane = Factory.obj( {
        init: function() {
            var nargs = arguments.length;
            
            if ( !nargs ) {
                // no params, set to default plane
                this.nNormal = module.Vec3.xAxis.Copy();
                this.dOffset = 0.0;
                
            } else if ( (nargs == 1) && arguments[0].isClass(module.Plane) ) {
                // 1 param of type Plane
                this.nNormal = arguments[0].nNormal.Copy();
                this.dOffset = arguments[0].dOffset;
                
            } else if ( (nargs == 2) && arguments[0].isClass(module.Vec3) ) {
                // 2 params - normal vector and the offset
                initUsingEqnCoeffs.call(this, arguments[0].x, arguments[0].y, arguments[0].z, arguments[1]);
                
            } else if ( (nargs == 3) && arguments[0].isClass(module.Point3) && arguments[1].isClass(module.Point3) &&
                        arguments[2].isClass(module.Point3) ) {
                // 3 parmas - P, Q, R
                initUsingPtsOnPlane.call(this, arguments[0], arguments[1], arguments[2])
            
            } else if ( nargs == 4 ) {
                // 4 parmas - a,b,c,d
                initUsingEqnCoeffs.call(this, arguments[0], arguments[1], arguments[2], arguments[3] );
            
            }
        }, // init()

		// apply transform to plane
		// requires the Quaternion and Mat33 libs
        transform: function(sF, rQ, tV) {
            var plane = Plane(),
                transOp;
            
            // we need to form the transformation matrix first
            // a) rQ is a rotaion quaternion so transform to a Mat33 matrix
            // b) add the scaling factor sF to the matrix
            // c) apply transform to direction vector
            // d) add the translate vector tV to the matrix
            // e) apply transform to origin vector

            /**
                // get the rotation and scale transforms first
                if (rQ instanceof Quat) transOp = rQ.matrix();
				
                plane.nNormal = transOp.transform(this.nNormal).scaleUpBy(1.0/sF);
                
                // now add the translate bit into the transform
                newTran = transOp.translate(tV);
                plane.dOffset = -newTran.dot(this.nNormal)/sF + this.dOffset;
                
                return plane;
                
             */
        },

		// return closest point on the plane to a point P
		ClosestPoint: function(P) {
            var v = this.nNormal.ScaleUp(this.test(P)); // note a new object is returned.
                
            // return P.Minus(v);  // creates another object, so lets use...
            return v.minus(P).negation();  // ie. (P-v) is same as -(v-P) and no need to create a 2nd object!
		},
        
        // distance to point P from plane
        distance: function(P) {
            return module.Includes.abs(this.test(P));
        },

		// test a point against the plane to see which side it lies
		test: function (P) {
			return this.nNormal.dot(P) + this.dOffset;
		},
		
		isEqual: function(plane) {
			return (plane.nNormal.isEqual(this.nNormal) && plane.dOffset == this.dOffset);
		},

		isNotEqual: function(plane) {
			return !(plane.nNormal.isEqual(this.nNormal) && plane.dOffset == this.dOffset);
		},

        // accessor and setter methods
		getNormal: function() {
			return this.nNormal;
		},

		getOffset: function() {
			return this.dOffset;
		},

		get: function() {
			// note we're not returning copies
			return {
				nNormal: this.nNorm,
				dOffset: this.dOffset
			};
		},
		
        set: function() {
            this.init.apply(this, arguments);
			return this;
		},

        toArray: function() {
            return [this.nNormal.toArray(), this.dOffset].join('');
        },
        
		// debug
		log: function() {
            return [this.nNormal.log(), this.dOffset].join(' ');
		}

	} ); //module.Plane = Factory.obj
    
	/**
	 * internal helper functions.
	 **/
     
	function initUsingEqnCoeffs(a, b, c, d) {
		var nsq = a*a + b*b + c*c;		// get the n.n value, where n = (a,b,c)
		
		if ( module.Includes.isZero(nsq) ) {
			// the plane's normal is a zero vector!  let's set defaults
			this.nNormal = module.Vec3.xAxis.Copy();
			this.dOffset = 0.0;
		} else
		{
			var nNorm = 1.0 / module.Includes.sqrt( nsq );		// ensure we normalise
			this.nNormal = module.Vec3(a*nNorm, b*nNorm, c*nNorm);
			this.dOffset = d*nNorm;
		}
	} // end of helper initUsingEqnCoeffs()
	
	function initUsingPtsOnPlane(P, Q, R) {
		var u = Q.Minus(P),
			v = R.Minus(P),
			n = u.Cross(v);
			nsq = n.lengthSquared();

		if ( module.Includes.isZero(nsq) ) {
			// the plane's normal is a zero vector!  let's set defaults
			this.nNormal = module.Vec3.xAxis.Copy();
			this.dOffset = 0.0;
		} else
		{
			var nNorm = 1.0 / module.Includes.sqrt( nsq );		// ensure we normalise
			this.nNormal = n.scaleUp(nNorm);
			this.dOffset = -this.nNormal.dot(P);
		}
	} // end of helper initUsingPtsOnPlane()


    // friend methods ---------------------------------------------
    //
    var friends = {
		// distance between plane and point
		distance: function(plane, P) {
			return module.Includes.abs( plane.test( P ) );
		}

    };  // end of friends{}

    for (var f in friends) {
        friends.hasOwnProperty(f) && (module.Plane[f] = friends[f]);
    };

} )(mLIB);  // function(module)
